package edu.kit.csl.tests.pisa.datatypes;

/*
This file is part of the PISA Alignment Tool.

Copyright (C) 2013
Karlsruhe Institute of Technology
Cognitive Systems Lab (CSL)
Felix Stahlberg

PISA is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

PISA is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with PISA. If not, see <http://www.gnu.org/licenses/>.
*/

import static org.junit.Assert.fail;

import java.util.Arrays;
import java.util.Iterator;

import junit.framework.Assert;

import org.junit.Before;
import org.junit.Test;

import edu.kit.csl.pisa.datatypes.Alignment;
import edu.kit.csl.pisa.datatypes.AlignmentPosition;
import edu.kit.csl.pisa.datatypes.AlignmentSourceWord;
import edu.kit.csl.pisa.datatypes.Dictionary;
import edu.kit.csl.pisa.datatypes.Sentence;

/**
 * Test cases for {@link edu.kit.csl.pisa.datatypes.Alignment}.
 */
public class AlignmentTest {

	private class TestAlignment extends Alignment {
		public TestAlignment(TestAlignment from) {
			super(from);
		}
		public TestAlignment(int srcLen, short[] alignment,
				boolean[] wordBoundaries) {
			super(srcLen, alignment, wordBoundaries);
		}
		public TestAlignment(int srcLen, short[] alignment,
				boolean[] wordBoundaries, boolean[] locks) {
			super(srcLen, alignment, wordBoundaries, locks);
		}
		public void assertEqualsAndConsistent(TestAlignment a2) {
			if (!this.equals(a2)) {
				fail("Equals method returned false");
			}
			short[] lastRelPos = new short[fertilities.length];
			Arrays.fill(lastRelPos, (short) -1);
			short lastStartPosition = -1;
			
			for (short i = 0; i < nTrgtWords; i++) {
				short srcWord = wordAlignment[i];
				lastRelPos[srcWord]++;
				Assert.assertEquals(lastRelPos[srcWord],
						this.relTrgtPositions[i]);
				Assert.assertEquals(lastRelPos[srcWord],
						a2.relTrgtPositions[i]);
				Assert.assertTrue(startPositions[i] > lastStartPosition);
				lastStartPosition = startPositions[i];
			}
			Assert.assertTrue(startPositions[nTrgtWords] > lastStartPosition);
			Assert.assertEquals(startPositions[nTrgtWords],
					a2.startPositions[nTrgtWords]); // Guardian
			for (int i = 0; i < fertilities.length; i++) {
				byte corrFert = (byte) (lastRelPos[i] + 1);
				Assert.assertEquals(corrFert, this.fertilities[i]);
				Assert.assertEquals(corrFert, a2.fertilities[i]);
			}
		}
	}
	
	private TestAlignment a1;
	
	private Sentence e = new Sentence("NULL Hello World", new Dictionary());
	private Sentence f = new Sentence("n1 n1 w1 w1_ w2 h1", new Dictionary());
	
	/**
	 * Initializes test alignments.
	 * 
	 * @throws java.lang.Exception
	 */
	@Before
	public void setUp() throws Exception {
		a1 = new TestAlignment(3,
			new short[]	 {0, 	0,     2,    2,     2,    1},
			new boolean[]{true, false, true, false, true, true});
	}

	/**
	 * Test method for {@link edu.kit.csl.pisa.datatypes.Alignment#combine(Alignment)}.
	 */
	@Test
	public final void testCombine() {
		a1.combine(new TestAlignment(3,
				new short[]	 {1, 	1,     1,    0,      1,     2},
				new boolean[]{true, false, false, true, true, true}));
		a1.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {0, 	0,     2,    0,     1,     2},
				new boolean[]{true, false, true, true, true, true}));
		a1.combine(new TestAlignment(3,
				new short[]	 {2, 	2,     2,     2,     1,     1},
				new boolean[]{true, false, false, false, true, false}));
		a1.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {0, 	0,     2,    2,     1,    1},
				new boolean[]{true, false, true, false, true, false}));
	}
	
	/**
	 * Test method for {@link edu.kit.csl.pisa.datatypes.Alignment#countTargetWords()}.
	 */
	@Test
	public final void testCountTargetWords() {
		Assert.assertEquals(4, a1.countTargetWords());
	}

	/**
	 * Test method for {@link edu.kit.csl.pisa.datatypes.Alignment#eachTargetPhoneme(edu.kit.csl.pisa.datatypes.Sentence, edu.kit.csl.pisa.datatypes.Sentence)}.
	 */
	@Test
	public final void testEachTargetPhoneme() {
		short iter = 0;
		short[] phonemeSequence = new short[] {0, 0, 2, 2, 2, 1};
		Iterable<AlignmentPosition> set = new Iterable<AlignmentPosition>() {
			@Override
			public Iterator<AlignmentPosition> iterator() {
				return a1.eachTargetPhoneme(e, f);
			}
		};
		for (AlignmentPosition pos : set) {
			Assert.assertEquals(phonemeSequence[iter],
					pos.getSourceWordPosition());
			Assert.assertTrue(pos.getTargetWordPhonemePosition() < 2);
			iter++;
		}
		Assert.assertEquals(6, iter);
	}

	/**
	 * Test method for {@link edu.kit.csl.pisa.datatypes.Alignment#eachTargetWord(edu.kit.csl.pisa.datatypes.Sentence, edu.kit.csl.pisa.datatypes.Sentence)}.
	 */
	@Test
	public final void testEachTargetWord() {
		short iter = 0;
		short[] wordLengths = new short[] {2, 2, 1, 1};
		Iterable<AlignmentPosition> set = new Iterable<AlignmentPosition>() {
			@Override
			public Iterator<AlignmentPosition> iterator() {
				return a1.eachTargetWord(e, f);
			}
		};
		for (AlignmentPosition pos : set) {
			Assert.assertEquals(wordLengths[iter], pos.getTargetWordLength());
			Assert.assertEquals(iter, pos.getTargetWordPosition());
			Assert.assertTrue(pos.getTargetRelativeWordPosition() < 2);
			iter++;
		}
		Assert.assertEquals(4, iter);
	}

	/**
	 * Test method for {@link edu.kit.csl.pisa.datatypes.Alignment#eachSourceWord(edu.kit.csl.pisa.datatypes.Sentence, edu.kit.csl.pisa.datatypes.Sentence)}.
	 */
	@Test
	public final void testEachSourceWord() {
		short iter = 0;
		short[] fertilities = new short[] {1, 1, 2};
		Iterable<AlignmentSourceWord> set = new Iterable<AlignmentSourceWord>() {
			@Override
			public Iterator<AlignmentSourceWord> iterator() {
				return a1.eachSourceWord(e, f);
			}
		};
		for (AlignmentSourceWord srcWord : set) {
			Assert.assertEquals(iter, srcWord.getSrcWordPos());
			Assert.assertEquals(fertilities[iter], srcWord.getFertility());
			iter++;
		}
		Assert.assertEquals(3, iter);
	}

	/**
	 * Test method for {@link edu.kit.csl.pisa.datatypes.Alignment#getNullFertility()}.
	 */
	@Test
	public final void testGetNullFertility() {
		Assert.assertEquals(1, a1.getNullFertility());
	}

	/**
	 * Test method for {@link edu.kit.csl.pisa.datatypes.Alignment#distanceTo(edu.kit.csl.pisa.datatypes.Alignment)}.
	 */
	@Test
	public final void testDistanceTo() {
		Assert.assertEquals(0, a1.distanceTo(a1));
		Assert.assertEquals(1, a1.distanceTo(new TestAlignment(3,
				new short[]	 {0, 	0,     1,    2,     2,    1},
				new boolean[]{true, false, true, false, true, true})));
		Assert.assertEquals(2, a1.distanceTo(new TestAlignment(3,
				new short[]	 {0, 	0,     1,    2,     2},
				new boolean[]{true, false, true, false, true})));
		Assert.assertEquals(4, a1.distanceTo(new TestAlignment(3,
				new short[]	 {2},
				new boolean[]{false})));
	}

	/**
	 * Test method for {@link edu.kit.csl.pisa.datatypes.Alignment#insertRelativeWordBoundary(short, short)}.
	 */
	@Test
	public final void testInsertRelativeWordBoundary() {
		a1.insertRelativeWordBoundary((short) 2, (short) 2);
		a1.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {0, 	0,     1,    1,     2,    1},
				new boolean[]{true, false, true, false, true, true}));
		a1.insertRelativeWordBoundary((short) 1, (short) 2);
		a1.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {0, 	2,     1,    1,     2,    1},
				new boolean[]{true, true,  true, false, true, true}));
		a1.insertRelativeWordBoundary((short) 3, (short) -2);
		a1.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {0, 	2,     1,    0,    2,    1},
				new boolean[]{true, true,  true, true, true, true}));
	}

	/**
	 * Test method for {@link edu.kit.csl.pisa.datatypes.Alignment#removeWordBoundary(int, int)}.
	 */
	@Test
	public final void testRemoveWordBoundary() {
		a1.removeWordBoundary(2, 0);
		a1.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]  {0,     0,      2,    2,      2,    2},
				new boolean[]{true, false, true, false, true, false}));
		a1.removeWordBoundary(0, 0);
		a1.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {0, 	0,     0,     0,     2,    2},
				new boolean[]{true, false, false, false, true, false}));
		a1.removeWordBoundary(0, 1);
		a1.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {2, 	2,     2,     2,     2,    2},
				new boolean[]{true, false, false, false, false, false}));
	}

	/**
	 * Test method for {@link edu.kit.csl.pisa.datatypes.Alignment#moveWordBoundary(int, int)}.
	 */
	@Test
	public final void testMoveWordBoundary() {
		a1.moveWordBoundary(0, -1);
		a1.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {0, 	2,     2,    2,     2,    1},
				new boolean[]{true, true, false, false, true, true}));
		a1.moveWordBoundary(0, 2);
		a1.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {0, 	0,     0,     2,    2,    1},
				new boolean[]{true, false, false, true, true, true}));
		a1.moveWordBoundary(2, 2);
		a1.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {0, 	0,     0,     2,    2,    2},
				new boolean[]{true, false, false, true, true, false}));
		a1.moveWordBoundary(1, -2);
		a1.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {0, 	0,     0,     2,    2,    2},
				new boolean[]{true, false, false, true, false, false}));
	}

	/**
	 * Test method for {@link edu.kit.csl.pisa.datatypes.Alignment#realign(int, int)}.
	 */
	@Test
	public final void testRealign() {
		a1.realign(2, 1);
		a1.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {0, 	0,     2,    2,     1,    1},
				new boolean[]{true, false, true, false, true, true}));
		a1.realign(0, 1);
		a1.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {1, 	1,     2,    2,     1,    1},
				new boolean[]{true, false, true, false, true, true}));
		a1.realign(1, 1);
		a1.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {1, 	1,     1,    1,     1,    1},
				new boolean[]{true, false, true, false, true, true}));
		a1.realign(3, 0);
		a1.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {1, 	1,     1,    1,     1,    0},
				new boolean[]{true, false, true, false, true, true}));
		a1.realign(1, 0);
		a1.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {1, 	1,     0,    0,     1,    0},
				new boolean[]{true, false, true, false, true, true}));
	}
	
	/**
	 * Test method for {@link edu.kit.csl.pisa.datatypes.Alignment#forceLocksFrom()}.
	 */
	@Test
	public final void testForceLocksFrom() {
		/*Sentence srcSen = new Sentence("hallo du welt", Dictionary.getSingleton(Dictionary.SRC_DICT));
		Sentence trgtSen = new Sentence("a b c d e f g h i j", Dictionary.getSingleton(Dictionary.TRGT_DICT));
		System.out.println(lockAlign.toString(srcSen, trgtSen));
		System.out.println(modAlign.toString(srcSen, trgtSen));
		modAlign.forceLocksFrom(lockAlign);
		System.out.println(modAlign.toString(srcSen, trgtSen));*/
		
		TestAlignment lockAlign = new TestAlignment(3,
			new short[]	 {0, 	 0,     2,    2,     2,     2,     2,     2,     1,     0},
			new boolean[]{true,  false, true, false, false, false, false, false, true,  true},
			new boolean[]{false, false, true, true,  true,  true,  true,  true,  false, false});
		
		TestAlignment modAlign = new TestAlignment(lockAlign);
		modAlign.forceLocksFrom(lockAlign);
		modAlign.assertEqualsAndConsistent(lockAlign);
		lockAlign = modAlign;
		// Standard case
		modAlign = new TestAlignment(3,
			new short[]	 {0, 	1,    1,     0,    2,     2,    0,    0,     0,     1},
			new boolean[]{true, true, false, true, true, false, true, false, false, true});
		modAlign.forceLocksFrom(lockAlign);
		modAlign.assertEqualsAndConsistent(new TestAlignment(3,
			new short[]	 {0, 	 1,     2,    2,     2,     2,     2,     2,     0,    1},
			new boolean[]{true,  true,  true, false, false, false, false, false, true, true}));
		// One Word boundary in the middle
		modAlign = new TestAlignment(3,
			new short[]	 {0, 	1,    1,     1,     1,     0,    0,     0,     0,     1},
			new boolean[]{true, true, false, false, false, true, false, false, false, true});
		modAlign.forceLocksFrom(lockAlign);
		modAlign.assertEqualsAndConsistent(new TestAlignment(3,
			new short[]	 {0, 	 1,     2,    2,     2,     2,     2,     2,     0,    1},
			new boolean[]{true,  true,  true, false, false, false, false, false, true, true}));
		// Long word covering both boundaries at the end
		modAlign = new TestAlignment(3,
			new short[]	 {2, 	1,    1,     1,     1,     1,     1,     1,     1,     1},
			new boolean[]{true, true, false, false, false, false, false, false, false, false});
		modAlign.forceLocksFrom(lockAlign);
		modAlign.assertEqualsAndConsistent(new TestAlignment(3,
			new short[]	 {2, 	 1,     2,    2,     2,     2,     2,     2,     1,    1},
			new boolean[]{true,  true,  true, false, false, false, false, false, true, false}));
		// Long word covering both boundaries
		modAlign = new TestAlignment(3,
			new short[]	 {2, 	1,    1,     1,     1,     1,     1,     1,     1,     2},
			new boolean[]{true, true, false, false, false, false, false, false, false, true});
		modAlign.forceLocksFrom(lockAlign);
		modAlign.assertEqualsAndConsistent(new TestAlignment(3,
			new short[]	 {2, 	 1,     2,    2,     2,     2,     2,     2,     1,    2},
			new boolean[]{true,  true,  true, false, false, false, false, false, true, true}));
		// Last word starts at same boundary
		modAlign = new TestAlignment(3,
			new short[]	 {1, 	1,     0,    0,     0,     0,     0,     0,     0,     0},
			new boolean[]{true, false, true, false, false, false, false, false, false, false});
		modAlign.forceLocksFrom(lockAlign);
		modAlign.assertEqualsAndConsistent(new TestAlignment(3,
			new short[]	 {1, 	1,     2,    2,     2,     2,     2,     2,     0,    0},
			new boolean[]{true, false, true, false, false, false, false, false, true, false}));
		
		// More locks: begin, end, 1 phoneme
		lockAlign = new TestAlignment(3,
				new short[]	 {0, 	0,     2,     2,     1,     0,    0,     2,    2,     2},
				new boolean[]{true, false, true,  false, true, true,  false, true, false, false},
				new boolean[]{true, true,  false, false, true, false, false, true, true,  true});
		modAlign = new TestAlignment(lockAlign);
		modAlign.forceLocksFrom(lockAlign);
		modAlign.assertEqualsAndConsistent(lockAlign);
		lockAlign = modAlign;
		
		// Only 1 phoneme words
		modAlign = new TestAlignment(3,
				new short[]	 {1, 	2,     1,    0,     2,     0,     1,     0,    2,    1},
				new boolean[]{true, true,  true, true,  true,  true,  true,  true, true, true});
		modAlign.forceLocksFrom(lockAlign);
		modAlign.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {0, 	0,     1,    0,     1,     0,     1,     2,    2,     2},
				new boolean[]{true, false, true, true,  true,  true,  true,  true, false, false}));
		
		// Single word
		modAlign = new TestAlignment(3,
				new short[]	 {2, 	2,     2,     2,     2,     2,     2,     2,     2,     2},
				new boolean[]{true, false, false, false, false, false, false, false, false, false});
		modAlign.forceLocksFrom(lockAlign);
		modAlign.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {0, 	0,     2,    2,     1,    2,    2,     2,    2,     2},
				new boolean[]{true, false, true, false, true, true, false, true, false, false}));

		// Some random alignment
		modAlign = new TestAlignment(3,
				new short[]	 {1, 	2,    2,     2,     2,     0,    1,    1,     1,     2},
				new boolean[]{true, true, false, false, false, true, true, false, false, true});
		modAlign.forceLocksFrom(lockAlign);
		modAlign.assertEqualsAndConsistent(new TestAlignment(3,
				new short[]	 {0, 	0,     2,    2,     1,    0,    1,    2,    2,     2},
				new boolean[]{true, false, true, false, true, true, true, true, false, false}));
	}
	

	/**
	 * Test lock mechanisms.
	 */
	@Test
	public final void testLocks() {
		TestAlignment a1Locked = new TestAlignment(3,
				new short[]	 {0, 	 0,     2,    2,     2,     1},
				new boolean[]{true,  false, true, false, true,  true},
				new boolean[]{false, false, true, true,  false, false});
		a1Locked.realign(1, 0);
		a1Locked.assertEqualsAndConsistent(a1);
		a1Locked.moveWordBoundary(1, -1);
		a1Locked.assertEqualsAndConsistent(a1);
		a1Locked.moveWordBoundary(0, 1);
		a1Locked.assertEqualsAndConsistent(a1);
		a1Locked.removeWordBoundary(0, 0);
		a1Locked.assertEqualsAndConsistent(a1);
		a1Locked.removeWordBoundary(1, 0);
		a1Locked.assertEqualsAndConsistent(a1);
		a1Locked.insertRelativeWordBoundary((short) 3, (short) -1);
		a1Locked.insertRelativeWordBoundary((short) 4, (short) -1);
		a1Locked.assertEqualsAndConsistent(a1);
	}
}
